// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>

#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/time/time_override.h"
#include "components/cast_certificate/cast_cert_reader.h"
#include "components/cast_certificate/cast_cert_test_helpers.h"
#include "components/cast_channel/cast_auth_util.h"
#include "components/cast_channel/cast_auth_util_fuzzer_shared.h"
#include "components/cast_channel/fuzz_proto/fuzzer_inputs.pb.h"
// Generated by the "cast_auth_util_fuzzer_certs" data_headers target.
#include "components/test/data/cast_certificate/certificates/chromecast_gen1_data.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/test/test_certificate_data.h"
#include "testing/libfuzzer/proto/lpm_interface.h"

namespace cast_channel {
namespace fuzz {
namespace {

const uint8_t kCertData[] = {
// Generated by //net/data/ssl/certificates:generate_fuzzer_cert_includes
#include "net/data/ssl/certificates/wildcard.inc"
};

base::NoDestructor<std::vector<std::string>> certs;

static bool InitializeOnce() {
  *certs = cast_certificate::ReadCertificateChainFromString(
      openscreen::cast::kChromecastGen1);
  CHECK(certs->size() >= 1)
      << "We should always have at least one certificate.";
  return true;
}

void UpdateTime(TimeBoundCase c, const base::Time* time, int direction) {
  auto& mtime = const_cast<base::Time&>(*time);
  switch (c) {
    case TimeBoundCase::VALID:
      // Create bound that include the current date.
      mtime = base::Time::Now() + base::Days(direction);
      break;
    case TimeBoundCase::INVALID:
      // Create a bound that excludes the current date.
      mtime = base::Time::Now() + base::Days(-direction);
      break;
    case TimeBoundCase::OOB:
      // Create a bound so far in the past/future it's not valid.
      mtime = base::Time::Now() + base::Days(direction * 10000);
      break;
    case TimeBoundCase::MISSING:
      // Remove any existing bound.
      mtime = base::Time();
      break;
    default:
      NOTREACHED();
  }
}

DEFINE_PROTO_FUZZER(CastAuthUtilInputs& input_union) {
  static bool init = InitializeOnce();
  CHECK(init);

  if (input_union.input_case() !=
      CastAuthUtilInputs::kAuthenticateChallengeReplyInput) {
    return;
  }

  auto& input = *input_union.mutable_authenticate_challenge_reply_input();

  SetupAuthenticateChallengeReplyInput(*certs, &input);

  // Build a well-formed cert with start and expiry times relative to the
  // current time.  The actual cert doesn't matter for testing purposes
  // because validation failures are ignored.
  scoped_refptr<net::X509Certificate> peer_cert =
      net::X509Certificate::CreateFromBytes(kCertData);
  UpdateTime(input.start_case(), &peer_cert->valid_start(), -1);
  UpdateTime(input.expiry_case(), &peer_cert->valid_expiry(), +1);

  AuthContext context = AuthContext::CreateForTest(input.nonce());

  AuthenticateChallengeReply(input.cast_message(), *peer_cert, context);
}

}  // namespace
}  // namespace fuzz
}  // namespace cast_channel
